﻿-- Path of Building
--
-- Module: Import Tab
-- Import/Export tab for the current build.
--
local launch, main = ...

local ipairs = ipairs
local t_insert = table.insert

local ImportTabClass = common.NewClass("ImportTab", "ControlHost", "Control", function(self, build)
	self.ControlHost()
	self.Control()

	self.build = build

	self.charImportMode = build.targetVersion == liveTargetVersion and "GETACCOUNTNAME" or "VERSIONWARNING"
	self.charImportStatus = "Idle"
self.controls.sectionCharImport = common.New("SectionControl", {"TOPLEFT",self,"TOPLEFT"}, 10, 18, 600, 250, "【导入国服角色】")
	self.controls.charImportVersionWarning = common.New("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 20, 0, 16, colorCodes.WARNING..[[
角色导入可能会出现问题，
比如游戏版本和pob支持的版本不同。
一些天赋点可能出现错位，一些新技能可能导入失败。
最好导入前确定下pob是否支持该版本。]])
	self.controls.charImportVersionWarning.shown = function()
		return self.charImportMode == "VERSIONWARNING"
	end
self.controls.charImportVersionWarningGo = common.New("ButtonControl", {"TOPLEFT",self.controls.charImportVersionWarning,"TOPLEFT"}, 0, 70, 80, 20, "继续", function()
		self.charImportMode = "GETACCOUNTNAME"
	end)
	self.controls.charImportStatusLabel = common.New("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 14, 200, 16, function()
return "^7角色导入状态: "..self.charImportStatus
	end)
	self.controls.charImportStatusLabel.shown = function()
		return self.charImportMode ~= "VERSIONWARNING"
	end

	-- Stage: input account name
self.controls.accountNameHeader = common.New("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 40, 200, 16, "^7请输入你的论坛名（登录官网论坛头像下的那个）:")
	self.controls.accountNameHeader.shown = function()
		return self.charImportMode == "GETACCOUNTNAME"
	end
	self.controls.accountName = common.New("EditControl", {"TOPLEFT",self.controls.accountNameHeader,"BOTTOMLEFT"}, 0, 4, 200, 20, main.lastAccountName or "", nil, "%c")
	self.controls.accountName.pasteFilter = function(text)
		return text:gsub("[\128-\255]",function(c)
			return codePointToUTF8(c:byte(1)):gsub(".",function(c)
				return string.format("%%%X", c:byte(1))
			end)
		end)
	end
self.controls.accountNameGo = common.New("ButtonControl", {"LEFT",self.controls.accountName,"RIGHT"}, 8, 0, 60, 20, "开始", function()
		self.controls.sessionInput.buf = ""
		self:DownloadCharacterList()
	end)
	self.controls.accountNameGo.enabled = function()
		return self.controls.accountName.buf:match("%S")
	end
	
self.controls.accountNameUnicode = common.New("LabelControl", {"TOPLEFT",self.controls.accountName,"BOTTOMLEFT"}, 0, 16, 0, 14, "^7注意！你需要先去官网公开你的角色.")
self.controls.accountNameURLEncoder = common.New("ButtonControl", {"TOPLEFT",self.controls.accountNameUnicode,"BOTTOMLEFT"}, 0, 4, 270, 18, "^x4040FFhttps://poe.game.qq.com/login/tencent", function()
OpenURL("https://poe.game.qq.com/login/tencent")
	end)

self.controls.accountNameUnicode2 = common.New("LabelControl", {"TOPLEFT",self.controls.accountNameURLEncoder,"BOTTOMLEFT"}, 0, 16, 0, 14, "^7如果你的名字带有特殊符号，可以去下面网站urlencode转码.")
self.controls.accountNameURLEncoder2 = common.New("ButtonControl", {"TOPLEFT",self.controls.accountNameUnicode2,"BOTTOMLEFT"}, 0, 4, 270, 18, "^x4040FFhttp://tool.chinaz.com/tools/urlencode.aspx", function()
OpenURL("http://tool.chinaz.com/tools/urlencode.aspx")
	end)

	
	-- Stage: input POESESSID
	self.controls.sessionHeader = common.New("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 40, 200, 14)
	self.controls.sessionHeader.label = function()
		return [[
^7无法获取账户']]..self.controls.accountName.buf..[['的角色列表，可能的原因有 :
1. 账户名输入错误
2、没有公开自己的角色
如果这是你的账户，你可以考虑
到官网的个人中心 --隐私设定 -取消勾选“隐藏角色标签”
或者在下面输入你的 POESESSID。
你可以在登录官网后浏览器的cookies中拿到。
		]]
	end
	self.controls.sessionHeader.shown = function()
		return self.charImportMode == "GETSESSIONID"
	end
self.controls.sessionRetry = common.New("ButtonControl", {"TOPLEFT",self.controls.sessionHeader,"TOPLEFT"}, 0, 108, 60, 20, "重试", function()
		self.controls.sessionInput.buf = ""
		self:DownloadCharacterList()
	end)
self.controls.sessionCancel = common.New("ButtonControl", {"LEFT",self.controls.sessionRetry,"RIGHT"}, 8, 0, 60, 20, "取消", function()
		self.charImportMode = "GETACCOUNTNAME"
		self.charImportStatus = "Idle"
	end)
	self.controls.sessionInput = common.New("EditControl", {"TOPLEFT",self.controls.sessionRetry,"BOTTOMLEFT"}, 0, 8, 350, 20, "", "POESESSID", "%X", 32)
	self.controls.sessionGo = common.New("ButtonControl", {"LEFT",self.controls.sessionInput,"RIGHT"}, 8, 0, 60, 20, "Go", function()
		self:DownloadCharacterList()
	end)
	self.controls.sessionGo.enabled = function()
		return #self.controls.sessionInput.buf == 32
	end

	-- Stage: select character and import data
self.controls.charSelectHeader = common.New("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 40, 200, 16, "^7选择要导入的角色:")
	self.controls.charSelectHeader.shown = function()
		return self.charImportMode == "SELECTCHAR" or self.charImportMode == "IMPORTING"
	end
self.controls.charSelectLeagueLabel = common.New("LabelControl", {"TOPLEFT",self.controls.charSelectHeader,"BOTTOMLEFT"}, 0, 6, 0, 14, "^7联盟:")
	self.controls.charSelectLeague = common.New("DropDownControl", {"LEFT",self.controls.charSelectLeagueLabel,"RIGHT"}, 4, 0, 150, 18, nil, function(index, value)
		self:BuildCharacterList(value.league)
	end)
	self.controls.charSelect = common.New("DropDownControl", {"TOPLEFT",self.controls.charSelectHeader,"BOTTOMLEFT"}, 0, 24, 400, 18)
	self.controls.charSelect.enabled = function()
		return self.charImportMode == "SELECTCHAR"
	end
self.controls.charImportHeader = common.New("LabelControl", {"TOPLEFT",self.controls.charSelect,"BOTTOMLEFT"}, 0, 16, 200, 16, "导入:")
self.controls.charImportTree = common.New("ButtonControl", {"LEFT",self.controls.charImportHeader, "RIGHT"}, 8, 0, 170, 20, "天赋树和珠宝", function()
		if self.build.spec:CountAllocNodes() > 0 then
main:OpenConfirmPopup("角色导入", "导入天赋树会覆盖你当前的天赋树.", "导入", function()
				self:DownloadPassiveTree()
			end)
		else
			self:DownloadPassiveTree()
		end
	end)
	self.controls.charImportTree.enabled = function()
		return self.charImportMode == "SELECTCHAR"
	end
self.controls.charImportTreeClearJewels = common.New("CheckBoxControl", {"LEFT",self.controls.charImportTree,"RIGHT"}, 90, 0, 18, "删除珠宝:")
self.controls.charImportTreeClearJewels.tooltipText = "导入时删除原有珠宝信息."
self.controls.charImportItems = common.New("ButtonControl", {"LEFT",self.controls.charImportTree, "LEFT"}, 0, 36, 110, 20, "装备和技能", function()
		self:DownloadItems()
	end)
	self.controls.charImportItems.enabled = function()
		return self.charImportMode == "SELECTCHAR"
	end
self.controls.charImportItemsClearSkills = common.New("CheckBoxControl", {"LEFT",self.controls.charImportItems,"RIGHT"}, 85, 0, 18, "删除技能:")
self.controls.charImportItemsClearSkills.tooltipText = "导入时删除原有技能信息."
self.controls.charImportItemsClearItems = common.New("CheckBoxControl", {"LEFT",self.controls.charImportItems,"RIGHT"}, 220, 0, 18, "删除装备:")
self.controls.charImportItemsClearItems.tooltipText = "导入时删除原有所有装备"
self.controls.charBanditNote = common.New("LabelControl", {"TOPLEFT",self.controls.charImportHeader,"BOTTOMLEFT"}, 0, 50, 200, 14, "^7提示: 导入完成后要手动配置好盗贼任务,\n因为盗贼任务是不能导入的.")
self.controls.charDone = common.New("ButtonControl", {"TOPLEFT",self.controls.charImportHeader,"BOTTOMLEFT"}, 0, 90, 60, 20, "关闭", function()
		self.charImportMode = "GETACCOUNTNAME"
		self.charImportStatus = "Idle"
	end)

	-- Build import/export
self.controls.sectionBuild = common.New("SectionControl", {"TOPLEFT",self.controls.sectionCharImport,"BOTTOMLEFT"}, 0, 18, 600, 200, "Build分享")
self.controls.generateCodeLabel = common.New("LabelControl", {"TOPLEFT",self.controls.sectionBuild,"TOPLEFT"}, 6, 14, 0, 16, "^7生成代码给其他POB用户（仅限17173汉化版）:")
self.controls.generateCode = common.New("ButtonControl", {"LEFT",self.controls.generateCodeLabel,"RIGHT"}, 4, 0, 80, 20, "生成", function()
		self.controls.generateCodeOut:SetText(common.base64.encode(Deflate(self.build:SaveDB("code"))):gsub("+","-"):gsub("/","_"))
	end)
self.controls.generateCodeOut = common.New("EditControl", {"TOPLEFT",self.controls.generateCodeLabel,"BOTTOMLEFT"}, 0, 8, 250, 20, "", "代码", "%Z")
	self.controls.generateCodeOut.enabled = function()
		return #self.controls.generateCodeOut.buf > 0
	end
self.controls.generateCodeCopy = common.New("ButtonControl", {"LEFT",self.controls.generateCodeOut,"RIGHT"}, 8, 0, 60, 20, "复制", function()
		Copy(self.controls.generateCodeOut.buf)
		self.controls.generateCodeOut:SetText("")
	end)
	self.controls.generateCodeCopy.enabled = function()
		return #self.controls.generateCodeOut.buf > 0
	end
self.controls.generateCodePastebin = common.New("ButtonControl", {"LEFT",self.controls.generateCodeCopy,"RIGHT"}, 8, 0, 140, 20, "生成Pastebin地址", function()
		local id = LaunchSubScript([[
			local code, proxyURL = ...
			local curl = require("lcurl.safe")
			local page = ""
			local easy = curl.easy()
			easy:setopt_url("https://pastebin.com/api/api_post.php")
			easy:setopt(curl.OPT_POST, true)
			easy:setopt(curl.OPT_POSTFIELDS, "api_dev_key=c4757f22e50e65e21c53892fd8e0a9ff&api_paste_private=1&api_option=paste&api_paste_code="..code)
			easy:setopt(curl.OPT_ACCEPT_ENCODING, "")
			if proxyURL then
				easy:setopt(curl.OPT_PROXY, proxyURL)
			end
			easy:setopt_writefunction(function(data)
				page = page..data
				return true
			end)
			easy:perform()
			easy:close()
			if page:match("pastebin.com") then
				return page
			else
				return nil, page
			end
		]], "", "", self.controls.generateCodeOut.buf, launch.proxyURL)
		if id then
			self.controls.generateCodeOut:SetText("")
self.controls.generateCodePastebin.label = "生成中..."
			launch:RegisterSubScript(id, function(pasteLink, errMsg)
self.controls.generateCodePastebin.label = "生成Pastebin地址"
				if errMsg then
					main:OpenMessagePopup("Pastebin.com", "Error creating paste:\n"..errMsg)
				else
					self.controls.generateCodeOut:SetText(pasteLink)
				end
			end)
		end
	end)
	self.controls.generateCodePastebin.enabled = function()
		return #self.controls.generateCodeOut.buf > 0 and not self.controls.generateCodeOut.buf:match("pastebin%.com")
	end
self.controls.generateCodeNote = common.New("LabelControl", {"TOPLEFT",self.controls.generateCodeOut,"BOTTOMLEFT"}, 0, 4, 0, 14, "^7注意: 这个代码很长，你可以用【生成Pastebin地址】来简化.")
self.controls.importCodeHeader = common.New("LabelControl", {"TOPLEFT",self.controls.generateCodeNote,"BOTTOMLEFT"}, 0, 26, 0, 16, "^7从代码中导入（仅限17173国服版代码）:")
	self.controls.importCodeIn = common.New("EditControl", {"TOPLEFT",self.controls.importCodeHeader,"BOTTOMLEFT"}, 0, 4, 250, 20, "", nil, "^%w_%-=", nil, function(buf)
		if #buf == 0 then
			self.importCodeState = nil
			return
		end
		self.importCodeState = "INVALID"
		local xmlText = Inflate(common.base64.decode(buf:gsub("-","+"):gsub("_","/")))
		if not xmlText then
			return
		end
		if launch.devMode and IsKeyDown("SHIFT") then
			Copy(xmlText)
		end
		self.importCodeState = "VALID"
		self.importCodeXML = xmlText
		if not self.build.dbFileName then
			self.controls.importCodeMode.selIndex = 2
		end
	end)
	self.controls.importCodeState = common.New("LabelControl", {"LEFT",self.controls.importCodeIn,"RIGHT"}, 4, 0, 0, 16)
	self.controls.importCodeState.label = function()
return (self.importCodeState == "VALID" and colorCodes.POSITIVE.."代码错误") or (self.importCodeState == "INVALID" and colorCodes.NEGATIVE.."代码错误") or ""
	end
self.controls.importCodePastebin = common.New("ButtonControl", {"LEFT",self.controls.importCodeIn,"RIGHT"}, 90, 0, 160, 20, "从Pastebin导入...", function()
		self:OpenPastebinImportPopup()
	end)
self.controls.importCodeMode = common.New("DropDownControl", {"TOPLEFT",self.controls.importCodeIn,"BOTTOMLEFT"}, 0, 4, 160, 20, { "导入到当前build", "导入到新build" })
	self.controls.importCodeMode.enabled = function()
		return self.importCodeState == "VALID" and self.build.dbFileName
	end
self.controls.importCodeGo = common.New("ButtonControl", {"TOPLEFT",self.controls.importCodeMode,"BOTTOMLEFT"}, 0, 8, 60, 20, "导入", function()
		if self.controls.importCodeMode.selIndex == 1 then
main:OpenConfirmPopup("Build Import", colorCodes.WARNING.."警告:^7 导入到当前build会删除原有build信息", "导入", function()
				self.build:Shutdown()
				self.build:Init(self.build.dbFileName, self.build.buildName, self.importCodeXML)
				self.build.viewMode = "TREE"
			end)
		else
			self.build:Shutdown()
			self.build:Init(false, "Imported build", self.importCodeXML)
			self.build.viewMode = "TREE"
		end
	end)
	self.controls.importCodeGo.enabled = function()
		return self.importCodeState == "VALID"
	end
end)

function ImportTabClass:Load(xml, fileName)
	self.lastAccountHash = xml.attrib.lastAccountHash
	if self.lastAccountHash then
		for accountName in pairs(main.gameAccounts) do
			if common.sha1(accountName) == self.lastAccountHash then
				self.controls.accountName:SetText(accountName)
			end
		end
	end
	self.lastCharacterHash = xml.attrib.lastCharacterHash
end

function ImportTabClass:Save(xml)
	xml.attrib = {
		lastAccountHash = self.lastAccountHash,
		lastCharacterHash = self.lastCharacterHash,
	}
end

function ImportTabClass:Draw(viewPort, inputEvents)
	self.x = viewPort.x
	self.y = viewPort.y
	self.width = viewPort.width
	self.height = viewPort.height

	self:ProcessControlsInput(inputEvents, viewPort)

	main:DrawBackground(viewPort)

	self:DrawControls(viewPort)
end

function ImportTabClass:DownloadCharacterList()
	self.charImportMode = "DOWNLOADCHARLIST"
self.charImportStatus = "正在获取角色列表..."
	local accountName = self.controls.accountName.buf
	local sessionID = #self.controls.sessionInput.buf == 32 and self.controls.sessionInput.buf or (main.gameAccounts[accountName] and main.gameAccounts[accountName].sessionID)
	launch:DownloadPage("https://poe.game.qq.com/character-window/get-characters?accountName="..accountName, function(page, errMsg)
		if errMsg == "Response code: 403" then
self.charImportStatus = colorCodes.NEGATIVE.."角色没有公开."
			self.charImportMode = "GETSESSIONID"
			return
		elseif errMsg == "Response code: 404" then
self.charImportStatus = colorCodes.NEGATIVE.."论坛名错误."
			self.charImportMode = "GETACCOUNTNAME"
			return
		elseif errMsg then
self.charImportStatus = colorCodes.NEGATIVE.."获取角色列表失败，请重试 ("..errMsg:gsub("\n"," ")..")"
			self.charImportMode = "GETACCOUNTNAME"
			return
		end
		local charList, errMsg = self:ProcessJSON(page)
		if errMsg then
self.charImportStatus = colorCodes.NEGATIVE.."获取角色列表失败，请稍后重试"
			self.charImportMode = "GETACCOUNTNAME"
			return
		end
		--ConPrintTable(charList)
		if #charList == 0 then
self.charImportStatus = colorCodes.NEGATIVE.."这个账户没有角色."
			self.charImportMode = "GETACCOUNTNAME"
			return
		end
		-- GGG's character API has an issue where for /get-characters the account name is not case-sensitive, but for /get-passive-skills and /get-items it is.
		-- This workaround grabs the profile page and extracts the correct account name from one of the URLs.
		launch:DownloadPage("https://poe.game.qq.com/account/view-profile/"..accountName, function(page, errMsg)
			if errMsg then
self.charImportStatus = colorCodes.NEGATIVE.."获取角色列表失败，请重试 ("..errMsg:gsub("\n"," ")..")"
				self.charImportMode = "GETACCOUNTNAME"
				return
			end
			local realAccountName = page:match("/account/view%-profile/([^/]+)/characters"):gsub(".", function(c) if c:byte(1) > 127 then return string.format("%%%2X",c:byte(1)) else return c end end)
			if not realAccountName then
				self.charImportStatus = colorCodes.NEGATIVE.."Failed to retrieve character list."
				self.charImportMode = "GETSESSIONID"
				return
			end
			self.controls.accountName:SetText(realAccountName)
			accountName = realAccountName
			self.charImportStatus = "Character list successfully retrieved."
			self.charImportMode = "SELECTCHAR"
			self.lastAccountHash = common.sha1(accountName)
			main.lastAccountName = accountName
			main.gameAccounts[accountName] = main.gameAccounts[accountName] or { }
			main.gameAccounts[accountName].sessionID = sessionID
			local leagueList = { }
			for i, char in ipairs(charList) do
				if not isValueInArray(leagueList, char.league) then
					t_insert(leagueList, char.league)
				end
			end
			table.sort(leagueList)
			wipeTable(self.controls.charSelectLeague.list)
			t_insert(self.controls.charSelectLeague.list, {
				label = "All",
			})
			for _, league in ipairs(leagueList) do
				t_insert(self.controls.charSelectLeague.list, {
					label = league,
					league = league,
				})
			end				
			self.lastCharList = charList
			self:BuildCharacterList()
		end, sessionID and "POESESSID="..sessionID)
	end, sessionID and "POESESSID="..sessionID)
end

function ImportTabClass:BuildCharacterList(league)
	wipeTable(self.controls.charSelect.list)
	for i, char in ipairs(self.lastCharList) do
		if not league or char.league == league then
			t_insert(self.controls.charSelect.list, {
label = string.format("%s: 等级 %d %s 在 %s", char.name or "?", char.level or 0, char.class or "?", char.league or "?"),
				char = char,
			})
		end
	end
	table.sort(self.controls.charSelect.list, function(a,b)
		return a.char.name:lower() < b.char.name:lower()
	end)
	self.controls.charSelect.selIndex = 1
	if self.lastCharacterHash then
		for i, char in ipairs(self.controls.charSelect.list) do
			if common.sha1(char.char.name) == self.lastCharacterHash then
				self.controls.charSelect.selIndex = i
				break
			end
		end
	end
end

function ImportTabClass:DownloadPassiveTree()
	self.charImportMode = "IMPORTING"
	self.charImportStatus = "Retrieving character passive tree..."
	local accountName = self.controls.accountName.buf
	local sessionID = #self.controls.sessionInput.buf == 32 and self.controls.sessionInput.buf or (main.gameAccounts[accountName] and main.gameAccounts[accountName].sessionID)
	local charSelect = self.controls.charSelect
	local charData = charSelect.list[charSelect.selIndex].char
	launch:DownloadPage("https://poe.game.qq.com/character-window/get-passive-skills?accountName="..accountName.."&character="..charData.name, function(page, errMsg)
		self.charImportMode = "SELECTCHAR"
		if errMsg then
			self.charImportStatus = colorCodes.NEGATIVE.."Error importing character data, try again ("..errMsg:gsub("\n"," ")..")"
			return
		elseif page == "false" then
			self.charImportStatus = colorCodes.NEGATIVE.."Failed to retrieve character data, try again."
			return
		end
		self.lastCharacterHash = common.sha1(charData.name)
		self:ImportPassiveTreeAndJewels(page, charData)
	end, sessionID and "POESESSID="..sessionID)
end

function ImportTabClass:DownloadItems()
	self.charImportMode = "IMPORTING"
	self.charImportStatus = "Retrieving character items..."
	local accountName = self.controls.accountName.buf
	local sessionID = #self.controls.sessionInput.buf == 32 and self.controls.sessionInput.buf or (main.gameAccounts[accountName] and main.gameAccounts[accountName].sessionID)
	local charSelect = self.controls.charSelect
	local charData = charSelect.list[charSelect.selIndex].char
	launch:DownloadPage("https://poe.game.qq.com/character-window/get-items?accountName="..accountName.."&character="..charData.name, function(page, errMsg)
		self.charImportMode = "SELECTCHAR"
		if errMsg then
			self.charImportStatus = colorCodes.NEGATIVE.."Error importing character data, try again ("..errMsg:gsub("\n"," ")..")"
			return
		elseif page == "false" then
			self.charImportStatus = colorCodes.NEGATIVE.."Failed to retrieve character data, try again."
			return
		end
		self.lastCharacterHash = common.sha1(charData.name)
		self:ImportItemsAndSkills(page)
	end, sessionID and "POESESSID="..sessionID)
end

function ImportTabClass:ImportPassiveTreeAndJewels(json, charData)
	--local out = io.open("get-passive-skills.json", "w")
	--out:write(json)
	--out:close()
	local charPassiveData, errMsg = self:ProcessJSON(json)
	if errMsg then
		self.charImportStatus = colorCodes.NEGATIVE.."Error processing character data, try again later."
		return
	end
	self.charImportStatus = colorCodes.POSITIVE.."Passive tree and jewels successfully imported."
	--ConPrintTable(charPassiveData)
	if self.controls.charImportTreeClearJewels.state then
		for _, slot in pairs(self.build.itemsTab.slots) do
			if slot.selItemId ~= 0 and slot.nodeId then
				self.build.itemsTab:DeleteItem(self.build.itemsTab.items[slot.selItemId])
			end
		end
	end
	local sockets = { }
	for i, slot in pairs(charPassiveData.jewel_slots) do
		sockets[i] = tonumber(type(slot) == "number" and slot or slot.passiveSkill.hash)
	end
	for _, itemData in pairs(charPassiveData.items) do
		self:ImportItem(itemData, sockets)
	end
	self.build.itemsTab:PopulateSlots()
	self.build.itemsTab:AddUndoState()
	self.build.spec:ImportFromNodeList(charData.classId, charData.ascendancyClass, charPassiveData.hashes)
	self.build.spec:AddUndoState()
	self.build.characterLevel = charData.level
	self.build.controls.characterLevel:SetText(charData.level)
	self.build.buildFlag = true
end

function ImportTabClass:ImportItemsAndSkills(json)
	--local out = io.open("get-items.json", "w")
	--out:write(json)
	--out:close()
	local charItemData, errMsg = self:ProcessJSON(json)
	if errMsg then
		self.charImportStatus = colorCodes.NEGATIVE.."Error processing character data, try again later."
		return
	end
	if self.controls.charImportItemsClearItems.state then
		for _, slot in pairs(self.build.itemsTab.slots) do
			if slot.selItemId ~= 0 and not slot.nodeId then
				self.build.itemsTab:DeleteItem(self.build.itemsTab.items[slot.selItemId])
			end
		end
	end
	local skillOrder
	if self.controls.charImportItemsClearSkills.state then
		skillOrder = { }
		for _, socketGroup in ipairs(self.build.skillsTab.socketGroupList) do
			for _, gem in ipairs(socketGroup.gemList) do
				if gem.grantedEffect and not gem.grantedEffect.support then
					t_insert(skillOrder, gem.grantedEffect.name)
				end
			end
		end
		wipeTable(self.build.skillsTab.socketGroupList)
	end
self.charImportStatus = colorCodes.POSITIVE.."物品和技能导入成功."
	--ConPrintTable(charItemData)
	for _, itemData in pairs(charItemData.items) do
		self:ImportItem(itemData)
	end
	if skillOrder then
		local groupOrder = { }
		for index, socketGroup in ipairs(self.build.skillsTab.socketGroupList) do
			groupOrder[socketGroup] = index
		end
		table.sort(self.build.skillsTab.socketGroupList, function(a, b)
			local orderA
			for _, gem in ipairs(a.gemList) do
				if gem.grantedEffect and not gem.grantedEffect.support then
					local i = isValueInArray(skillOrder, gem.grantedEffect.name)
					if i and (not orderA or i < orderA) then
						orderA = i
					end
				end
			end
			local orderB
			for _, gem in ipairs(b.gemList) do
				if gem.grantedEffect and not gem.grantedEffect.support then
					local i = isValueInArray(skillOrder, gem.grantedEffect.name)
					if i and (not orderB or i < orderB) then
						orderB = i
					end
				end
			end
			if orderA and orderB then
				if orderA ~= orderB then
					return orderA < orderB
				else
					return groupOrder[a] < groupOrder[b]
				end
			elseif not orderA and not orderB then
				return groupOrder[a] < groupOrder[b]
			else
				return orderA
			end
		end)
	end
	self.build.itemsTab:PopulateSlots()
	self.build.itemsTab:AddUndoState()
	self.build.skillsTab:AddUndoState()
	self.build.characterLevel = charItemData.character.level
	self.build.controls.characterLevel:SetText(charItemData.character.level)
	self.build.buildFlag = true
	return charItemData.character -- For the wrapper
end

local rarityMap = { [0] = "普通", "魔法", "稀有", "传奇", [9] = "遗产" }
local slotMap = { ["Weapon"] = "Weapon 1", ["Offhand"] = "Weapon 2", ["Weapon2"] = "Weapon 1 Swap", ["Offhand2"] = "Weapon 2 Swap", ["Helm"] = "Helmet", ["BodyArmour"] = "Body Armour", ["Gloves"] = "Gloves", ["Boots"] = "Boots", ["Amulet"] = "Amulet", ["Ring"] = "Ring 1", ["Ring2"] = "Ring 2", ["Belt"] = "Belt" }

function ImportTabClass:ImportItem(itemData, sockets, slotName)
	if not slotName then
		if itemData.inventoryId == "PassiveJewels" and sockets then
			slotName = "Jewel "..sockets[itemData.x + 1]
		elseif itemData.inventoryId == "Flask" then
			slotName = "Flask "..(itemData.x + 1)
		else
			slotName = slotMap[itemData.inventoryId]
		end
	end
	if not slotName then
		-- Ignore any items that won't go into known slots
		return
	end

	local item = common.New("Item", self.build.targetVersion)

	-- Determine rarity, display name and base type of the item
	item.rarity = rarityMap[itemData.frameType]
	if #itemData.name > 0 then
		item.title = itemLib.sanitiseItemText(itemData.name)
		item.baseName = itemLib.sanitiseItemText(itemData.typeLine)
		item.name = item.title .. ", " .. item.baseName
		if item.baseName == "Two-Toned Boots" then
			-- Hack for Two-Toned Boots
			item.baseName = "Two-Toned Boots (Armour/Energy Shield)"
		end
		item.base = self.build.data.itemBases[item.baseName]
		if item.base then
			item.type = item.base.type
		else
			ConPrintf("Unrecognised base in imported item: %s", item.baseName)
		end
	else
		item.name = itemLib.sanitiseItemText(itemData.typeLine)
		for baseName, baseData in pairs(self.build.data.itemBases) do
			local s, e = item.name:find(baseName, 1, true)
			if s then
				item.baseName = baseName
				item.namePrefix = item.name:sub(1, s - 1)
				item.nameSuffix = item.name:sub(e + 1)
				item.type = baseData.type
				break
			end
		end
		if not item.baseName then
			local s, e = item.name:find("Two-Toned Boots", 1, true)
			if s then
				-- Hack for Two-Toned Boots
				item.baseName = "Two-Toned Boots (Armour/Energy Shield)"
				item.namePrefix = item.name:sub(1, s - 1)
				item.nameSuffix = item.name:sub(e + 1)
				item.type = "Boots"
			end
		end
		item.base = self.build.data.itemBases[item.baseName]
	end
	if not item.base or not item.rarity then
		return
	end

	-- Import item data
	item.uniqueID = itemData.id
	item.shaper = itemData.shaper
	item.elder = itemData.elder
	if itemData.ilvl > 0 then
		item.itemLevel = itemData.ilvl
	end
	if item.base.weapon or item.base.armour or item.base.flask then
		item.quality = 0
	end
	if itemData.properties then
		for _, property in pairs(itemData.properties) do
if property.name == "品质" then
				item.quality = tonumber(property.values[1][1]:match("%d+"))
elseif property.name == "范围" then
				for index, data in pairs(self.build.data.jewelRadius) do
					if property.values[1][1] == data.label then
						item.jewelRadiusIndex = index
						break
					end
				end
elseif property.name == "仅限" then
				item.limit = tonumber(property.values[1][1])
			elseif property.name == "Evasion Rating" then
				if item.baseName == "Two-Toned Boots (Armour/Energy Shield)" then
					-- Another hack for Two-Toned Boots
					item.baseName = "Two-Toned Boots (Armour/Evasion)"
					item.base = self.build.data.itemBases[item.baseName]
				end
			elseif property.name == "Energy Shield" then
				if item.baseName == "Two-Toned Boots (Armour/Evasion)" then
					-- Yet another hack for Two-Toned Boots
					item.baseName = "Two-Toned Boots (Evasion/Energy Shield)"
					item.base = self.build.data.itemBases[item.baseName]
				end
			end
		end
	end
	if itemData.corrupted then
		item.corrupted = true
	end
	if itemData.sockets and itemData.sockets[1] then
		item.sockets = { }
		for i, socket in pairs(itemData.sockets) do
			item.sockets[i] = { group = socket.group, color = socket.sColour }
		end
	end
	if itemData.socketedItems then
		self:ImportSocketedItems(item, itemData.socketedItems, slotName)
	end
	if itemData.requirements and (not itemData.socketedItems or not itemData.socketedItems[1]) then
		-- Requirements cannot be trusted if there are socketed gems, as they may override the item's natural requirements
		item.requirements = { }
		for _, req in ipairs(itemData.requirements) do
			if req.name == "Level" then
				item.requirements.level = req.values[1][1]
			end
		end
	end
	item.modLines = { }
	item.implicitLines = 0
	if itemData.implicitMods then
		item.implicitLines = item.implicitLines + #itemData.implicitMods
		for _, line in ipairs(itemData.implicitMods) do
			line = line:gsub("\n"," ")
			local modList, extra = modLib.parseMod[self.build.targetVersion](line)
			t_insert(item.modLines, { line = line, extra = extra, mods = modList or { } })
		end
	end
	if itemData.enchantMods then
		item.implicitLines = item.implicitLines + #itemData.enchantMods
		for _, line in ipairs(itemData.enchantMods) do
			line = line:gsub("\n"," ")
			local modList, extra = modLib.parseMod[self.build.targetVersion](line)
			t_insert(item.modLines, { line = line, extra = extra, mods = modList or { }, crafted = true })
		end
	end
	if itemData.explicitMods then
		for _, line in ipairs(itemData.explicitMods) do
			for line in line:gmatch("[^\n]+") do
				local modList, extra = modLib.parseMod[self.build.targetVersion](line)
				t_insert(item.modLines, { line = line, extra = extra, mods = modList or { } })
			end
		end
	end
	if itemData.craftedMods then
		for _, line in ipairs(itemData.craftedMods) do
			for line in line:gmatch("[^\n]+") do
				local modList, extra = modLib.parseMod[self.build.targetVersion](line)
				t_insert(item.modLines, { line = line, extra = extra, mods = modList or { }, crafted = true })
			end
		end
	end

	-- Add and equip the new item
	item:BuildAndParseRaw()
	--ConPrintf("%s", item.raw)
	if item.base then
		local repIndex, repItem
		for index, item in pairs(self.build.itemsTab.items) do
			if item.uniqueID == itemData.id then
				repIndex = index
				repItem = item
				break
			end
		end
		if repIndex then
			-- Item already exists in the build, overwrite it
			item.id = repItem.id
			self.build.itemsTab.items[item.id] = item
			item:BuildModList()
		else
			self.build.itemsTab:AddItem(item, true)
		end
		self.build.itemsTab.slots[slotName]:SetSelItemId(item.id)
	end
end

function ImportTabClass:ImportSocketedItems(item, socketedItems, slotName)
	-- Build socket group list
	local itemSocketGroupList = { }
	local abyssalSocketId = 1
	for _, socketedItem in ipairs(socketedItems) do
		if socketedItem.abyssJewel then
			self:ImportItem(socketedItem, nil, slotName .. " Abyssal Socket "..abyssalSocketId)
			abyssalSocketId = abyssalSocketId + 1
		else
			local gemInstance = { level = 20, quality = 0, enabled = true, enableGlobal1 = true }
			gemInstance.nameSpec = socketedItem.typeLine:gsub(" Support","")
			gemInstance.support = socketedItem.support
			for _, property in pairs(socketedItem.properties) do
if property.name == "等级" then
					gemInstance.level = tonumber(property.values[1][1]:match("%d+"))
elseif property.name == "品质" then
					gemInstance.quality = tonumber(property.values[1][1]:match("%d+"))
				end
			end
			local groupID = item.sockets[socketedItem.socket + 1].group
			if not itemSocketGroupList[groupID] then
				itemSocketGroupList[groupID] = { label = "", enabled = true, gemList = { }, slot = slotName }
			end
			local socketGroup = itemSocketGroupList[groupID]
			if not socketedItem.support and socketGroup.gemList[1] and socketGroup.gemList[1].support then
				-- If the first gemInstance is a support gemInstance, put the first active gemInstance before it
				t_insert(socketGroup.gemList, 1, gemInstance)
			else
				t_insert(socketGroup.gemList, gemInstance)
			end
		end
	end

	-- Import the socket groups
	for _, itemSocketGroup in pairs(itemSocketGroupList) do
		-- Check if this socket group matches an existing one
		local repGroup
		for index, socketGroup in pairs(self.build.skillsTab.socketGroupList) do
			if #socketGroup.gemList == #itemSocketGroup.gemList and (not socketGroup.slot or socketGroup.slot == slotName) then
				local match = true
				for gemIndex, gem in pairs(socketGroup.gemList) do
					if gem.nameSpec:lower() ~= itemSocketGroup.gemList[gemIndex].nameSpec:lower() then
						match = false
						break
					end
				end
				if match then
					repGroup = socketGroup
					break
				end
			end
		end
		if repGroup then
			-- Update the existing one
			for gemIndex, gem in pairs(repGroup.gemList) do
				local itemGem = itemSocketGroup.gemList[gemIndex]
				gem.level = itemGem.level
				gem.quality = itemGem.quality
			end
		else
			t_insert(self.build.skillsTab.socketGroupList, itemSocketGroup)
		end
		self.build.skillsTab:ProcessSocketGroup(itemSocketGroup)
	end	
end

function ImportTabClass:OpenPastebinImportPopup()
	local controls = { }
controls.editLabel = common.New("LabelControl", nil, 0, 20, 0, 16, "输入Pastebin.com连接（由17173国服版分享的）:")
	controls.edit = common.New("EditControl", nil, 0, 40, 250, 18, "", nil, "^%w%p%s", nil, function(buf)
		controls.msg.label = ""
	end)
	controls.msg = common.New("LabelControl", nil, 0, 58, 0, 16, "")
controls.import = common.New("ButtonControl", nil, -45, 80, 80, 20, "导入", function()
		controls.import.enabled = false
controls.msg.label = "获取中..."
		controls.edit.buf = controls.edit.buf:gsub("^%s+", ""):gsub("%s+$", "") -- Quick Trim
		launch:DownloadPage(controls.edit.buf:gsub("pastebin%.com/(%w+)%s*$","pastebin.com/raw/%1"), function(page, errMsg)
			if errMsg then
				controls.msg.label = "^1"..errMsg
				controls.import.enabled = true
			else
				self.controls.importCodeIn:SetText(page, true)
				main:ClosePopup()
			end
		end)
	end)
	controls.import.enabled = function()
		return #controls.edit.buf > 0 and controls.edit.buf:match("pastebin%.com/%w+")
	end
controls.cancel = common.New("ButtonControl", nil, 45, 80, 80, 20, "取消", function()
		main:ClosePopup()
	end)
main:OpenPopup(380, 110, "从Pastebin导入", controls, "导入", "编辑")
end

function ImportTabClass:ProcessJSON(json)
	local func, errMsg = loadstring("return "..jsonToLua(json))
	if errMsg then
		return nil, errMsg
	end
	setfenv(func, { }) -- Sandbox the function just in case
	local data = func()
	if type(data) ~= "table" then
		return nil, "Return type is not a table"
	end
	return data
end
